package org.lisen.scdemo.sender.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;


/**
 * RabbbitMQ配置类.
 *
 * 1.配置队列
 * 2.配置交换机
 * 3.配置交换机和队列的对应关系
 *
 * @author Administrator
 * @create 2020-02-1519:04
 */
@Configuration
@Slf4j
public class RabbitMQConfig {

    /**
     * 声明一个队列，队列名为“hello”，用于测试
     * SenderServiceImpl类中send()方法中使用rabbitTemplate向该队列中发送信息：
     * rabbitTemplate.convertAndSend("hello", msg);
     *
     * @return
     */
    @Bean
    public Queue helloQueue() {
        return new Queue("hello");
    }


    /**
     * 声明一个Direct类型的交换机，支持序列化。后面会和队列进行绑定
     * @return
     */
    @Bean(name="directExchange")
    public Exchange directExchange() {
        return ExchangeBuilder.directExchange("direct.exchange").durable(true).build();
    }


    /**
     * <p>
     * 声明一个队列，使用该队列与Direct类型的交换机绑定, 演示Direct类型的Exchange的使用。
     * </p>
     *
     * <p>
     * 队列参数说明如下：
     * </p>
     * <p>
     * <li> x-message-ttl：设置消息在队列中的存活时间，单位毫秒</li>
     * <li>x-max-length: 设置队列长度限制</li>
     * <li>x-max-length-bytes: 设置队列中消息的最大字节数</li>
     * <li>x-overflow: 队列消息溢出时该队列的行为，默认为drop-head：丢弃队列头部的消息， reject-publish: 拒绝接收</li>
     * <li>x-dead-letter-exchange, x-dead-letter-routing-key: 消息因为超时或超过限制在队列中消失，这种情况发生时系统
     *    会丢失一些消息，有时这些消息是我们需要获知的，rabbitmq的死信队列可以解决这个问题，设置了x-dead-letter-exchang,
     *    x-dead-letter-routing-key(两个需要同时设定)参数，那么因为超时或超过限制而被从队列中删除的消息会推到dead-exchange
     *    中，再根据routing-key推入到dead-queue中，需要时可以从dead-queue中获取这些消息。
     * </li>
     * <li>x-max-priority: 队列所支持的优先级别，例如设置为5，表示队列支持0到5个优先级别，5最高，0最低，消息生产者可以在
     *    发送消息时可以指定消息的优先级别，消息按照优先级别从高到低的顺序发送给消息消费者。
     * </li>
     * <li>alternate-exchange: 下面简称AE，当一个消息不能被route的时候，如果exchange设定了AE，则消息会被投递到AE。
     *     如果存在AE链，则会按此继续投递，直到消息被route或AE链结束或遇到已经尝试route过消息的AE
     * </li>
     * </p>
     *
     * @return
     */
    @Bean(name="directQueue")
    public Queue directQueue() {
        Map<String,Object> args = new HashMap<>();
        //args.put("x-message-ttl", 1000*60*20);
        args.put("x-max-length", 100);
        args.put("x-overflow","reject-publish");

        //配置死信队列，在消费者发生不可恢复异常时，将消息发送到死信队列中
        //在消费者消息确认时使用
        args.put("x-dead-letter-exchange","dxl.exchange");
        args.put("x-dead-letter-routing-key","routing.dxl.key");

        return QueueBuilder.durable("direct.queue").withArguments(args).build();
    }


    /**
     * 将交换机（directExchange）与队列（directQueue）绑定在一起。
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding directBinding(
            @Qualifier("directQueue") Queue queue,
            @Qualifier("directExchange") Exchange exchange) {

        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("direct.exchange.routing.key")
                .noargs();
    }


    /**
     * 声明Topic类型的交换机，支持序列化，后面队列进行绑定（topic.queue.q1，topic.queue.q2）
     * @return
     */
    @Bean(name="topicExchange")
    public Exchange topicExchange() {

        return ExchangeBuilder
                .topicExchange("topic.exchange")
                .durable(true)
                .build();
    }


    /**
     * 声明队列，该队列与topic交换机绑定
     * @return
     */
    @Bean(name="topicQueue1")
    public Queue topicQueue1() {
        return QueueBuilder.durable("topic.queue.q1").build();
    }


    /**
     * 声明队列，该队列与topic交换机绑定
     * @return
     */
    @Bean(name="topicQueue2")
    public Queue topicQueue2() {
        return QueueBuilder.durable("topic.queue.q2").build();
    }


    /**
     * 将队列（topic.queue.q1）与topic型交换机进行绑定
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding topicBindingQ1(
            @Qualifier("topicQueue1") Queue queue,
            @Qualifier("topicExchange") Exchange exchange)  {

        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("topic.queue1.#")
                .noargs();
    }


    /**
     * 将队列（topic.queue.q2）与topic型交换机进行绑定
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding topicBindingQ2(
            @Qualifier("topicQueue2") Queue queue,
            @Qualifier("topicExchange") Exchange exchange) {

        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("topic.queue2.#")
                .noargs();
    }


    /**
     * 声明fanout型交换机，支持持久化
     * @return
     */
    @Bean(name="fanoutExchange")
    public Exchange fanoutExchange() {
        return ExchangeBuilder.fanoutExchange("fanout.exchange").durable(true).build();
    }


    /**
     * 声明队列，用于与fanoutExchange交换机绑定
     * @return
     */
    @Bean(name="fanoutQueue1")
    public Queue fanoutQueue1() {
        return QueueBuilder.durable("fanout.queue1").build();
    }


    /**
     * 声明队列，用于与fanoutExchange交换机绑定
     * @return
     */
    @Bean(name="fanoutQueue2")
    public Queue fanoutQueue2() {
        return QueueBuilder.durable("fanout.queue2").build();
    }


    /**
     * 将队列fanout.queue1与fanout.exchange交换机进行绑定
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding fanoutBindingQ1(
            @Qualifier("fanoutQueue1") Queue queue,
            @Qualifier("fanoutExchange") Exchange exchange) {

        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("fanout.routing.key")
                .noargs();
    }


    /**
     * 将队列fanout.queue2与fanout.exchange交换机进行绑定
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding fanoutBindingQ2(
            @Qualifier("fanoutQueue2") Queue queue,
            @Qualifier("fanoutExchange") Exchange exchange) {

        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("fanout.routing.key")
                .noargs();
    }


    /**
     * 死信交换机，（为获取更多的灵活性，使用topic型交换机作为死信交换机）
     * @return
     */
    @Bean(name="dxlExchange")
    public Exchange dxlExchange() {
        return ExchangeBuilder.topicExchange("dxl.exchange").durable(true).build();
    }


    /**
     * 死信队列
     * @return
     */
    @Bean(name="dxlQueue")
    public Queue dxlQueue() {
        return QueueBuilder.durable("dxl.queue").build();
    }


    /**
     * 绑定死信队列和死信交换机
     * @param queue
     * @param exchange
     * @return
     */
    @Bean
    public Binding bindingDxl(
            @Qualifier("dxlQueue") Queue queue,
            @Qualifier("dxlExchange") Exchange exchange) {

        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("routing.dxl.key")
                .noargs();
    }


    /**
     * 创建一个直接交换机，用于演示死信队列的使用
     * @return
     */
    @Bean(name="usualExchange")
    public Exchange usualExchange() {
        return ExchangeBuilder.directExchange("usual.direct.exchange").durable(true).build();
    }


    /**
     * 创建一个队列，与死信交换机绑定，用于演示死信队列。
     * 在声明队列时，指定了与该队列关联的死信交换机和对应的路由键，
     * 当消息超时，或超过的了队列的最大长度，则被丢弃的消息将会被
     * 发送到私信交换机中，然后被存储在与私信交换机对应的死信队列中。
     *
     * @return
     */
    @Bean(name="usualQueue")
    public Queue usualQueue() {
        return QueueBuilder.durable("usual.queue")
                .withArgument("x-message-ttl", 1000*60*2)
                .withArgument("x-max-length", 5)
                .withArgument("x-dead-letter-exchange", "dxl.exchange")
                .withArgument("x-dead-letter-routing-key", "routing.dxl.key")
                .build();
    }


    /**
     * 绑定usual.queue队列与usual.direct.exchange交换机，用于演示死信队列
     */
    @Bean
    public Binding bindingUsualQueue(
            @Qualifier("usualQueue") Queue queue,
            @Qualifier("usualExchange") Exchange exchange) {

        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("routing.usual.key")
                .noargs();
    }


    /**
     * 死信交换机，用于实现延迟队列
     * @return
     */
    @Bean(name="delayDxlExchange")
    public Exchange delayDxlExchange() {
        return ExchangeBuilder.topicExchange("delay.dxl.exhange").durable(true).build();
    }


    /**
     * 死信队列，用于实现延迟队列. </p>
     *
     * 当队列中的消息过期则通过“delay.dxl.exhange”交换机将过期的消息发到该队列
     *
     * @return Queue
     */
    @Bean(name="delayDxlQueue")
    public Queue delayDxlQueue() {
        return QueueBuilder.durable("delay.dxl.queue").build();
    }


    /**
     * 将死信交换机与死信队列进行绑定，用于实现延迟队列
     * @return Binding
     */
    @Bean
    public Binding bindingDelayDxlExchangeAndDelayQueue(
            @Qualifier("delayDxlExchange") Exchange exchange,
            @Qualifier("delayDxlQueue") Queue queue) {

        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("routing.delay.dxlqueue.#")
                .noargs();
    }


    /**
     * 直接交换机，用于实现延迟队列，与该交换机绑定的队列需要指定过期时间参数
     * @return Exchange
     */
    @Bean(name="delayExchange")
    public Exchange delayExchange() {
        return ExchangeBuilder.directExchange("delay.exchange").durable(true).build();
    }


    /**
     * 声明队列，用于实现延迟队列，该队列指定超时时间
     * @return
     */
    @Bean(name="delayQueue")
    public Queue delayQueue() {
        return QueueBuilder
                .durable("delay.queue")
                .withArgument("x-message-ttl", 1000*60*1)
                .withArgument("x-dead-letter-exchange", "delay.dxl.exhange")
                .withArgument("x-dead-letter-routing-key","routing.delay.dxlqueue.key")
                .build();
    }


    /**
     * 绑定队列与交换机， 用于实现延迟队列
     * @param exchange 交换机
     * @param queue 队列
     * @return Binding
     */
    @Bean
    public Binding bindingDelayQueueAndDelayExchange(
            @Qualifier("delayExchange") Exchange exchange,
            @Qualifier("delayQueue") Queue queue
    ) {
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("routing.delay.key")
                .noargs();
    }

}
